<!DOCTYPE html>
<!--
Copyright (C) 2017 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-router</title>

<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-router.html">

<script>void(0);</script>

<test-fixture id="basic">
  <template>
    <gr-router></gr-router>
  </template>
</test-fixture>

<script>
  suite('gr-router tests', () => {
    let element;
    let sandbox;

    setup(() => {
      sandbox = sinon.sandbox.create();
      element = fixture('basic');
    });

    teardown(() => { sandbox.restore(); });

    test('_getHashFromCanonicalPath', () => {
      let url = '/foo/bar';
      let hash = element._getHashFromCanonicalPath(url);
      assert.equal(hash, '');

      url = '';
      hash = element._getHashFromCanonicalPath(url);
      assert.equal(hash, '');

      url = '/foo#bar';
      hash = element._getHashFromCanonicalPath(url);
      assert.equal(hash, 'bar');

      url = '/foo#bar#baz';
      hash = element._getHashFromCanonicalPath(url);
      assert.equal(hash, 'bar#baz');

      url = '#foo#bar#baz';
      hash = element._getHashFromCanonicalPath(url);
      assert.equal(hash, 'foo#bar#baz');
    });

    suite('_parseLineAddress', () => {
      test('returns null for empty and invalid hashes', () => {
        let actual = element._parseLineAddress('');
        assert.isNull(actual);

        actual = element._parseLineAddress('foobar');
        assert.isNull(actual);

        actual = element._parseLineAddress('foo123');
        assert.isNull(actual);

        actual = element._parseLineAddress('123bar');
        assert.isNull(actual);
      });

      test('parses correctly', () => {
        let actual = element._parseLineAddress('1234');
        assert.isOk(actual);
        assert.equal(actual.lineNum, 1234);
        assert.isFalse(actual.leftSide);

        actual = element._parseLineAddress('a4');
        assert.isOk(actual);
        assert.equal(actual.lineNum, 4);
        assert.isTrue(actual.leftSide);

        actual = element._parseLineAddress('b77');
        assert.isOk(actual);
        assert.equal(actual.lineNum, 77);
        assert.isTrue(actual.leftSide);
      });
    });

    test('_startRouter requires auth for the right handlers', () => {
      // This test encodes the lists of route handler methods that gr-router
      // automatically checks for authentication before triggering.

      const requiresAuth = {};
      const doesNotRequireAuth = {};
      sandbox.stub(Gerrit.Nav, 'setup');
      sandbox.stub(window.page, 'start');
      sandbox.stub(window.page, 'base');
      sandbox.stub(window, 'page');
      sandbox.stub(element, '_mapRoute', (pattern, methodName, usesAuth) => {
        if (usesAuth) {
          requiresAuth[methodName] = true;
        } else {
          doesNotRequireAuth[methodName] = true;
        }
      });
      element._startRouter();

      const actualRequiresAuth = Object.keys(requiresAuth);
      actualRequiresAuth.sort();
      const actualDoesNotRequireAuth = Object.keys(doesNotRequireAuth);
      actualDoesNotRequireAuth.sort();

      const shouldRequireAutoAuth = [
        '_handleAgreementsRoute',
        '_handleCreateGroupRoute',
        '_handleCreateProjectRoute',
        '_handleDiffEditRoute',
        '_handleGroupAuditLogRoute',
        '_handleGroupInfoRoute',
        '_handleGroupListFilterOffsetRoute',
        '_handleGroupListFilterRoute',
        '_handleGroupListOffsetRoute',
        '_handleGroupMembersRoute',
        '_handleGroupRoute',
        '_handlePluginListFilterOffsetRoute',
        '_handlePluginListFilterRoute',
        '_handlePluginListOffsetRoute',
        '_handlePluginListRoute',
        '_handleProjectCommandsRoute',
        '_handleSettingsLegacyRoute',
        '_handleSettingsRoute',
      ];
      assert.deepEqual(actualRequiresAuth, shouldRequireAutoAuth);

      const unauthenticatedHandlers = [
        '_handleBranchListFilterOffsetRoute',
        '_handleBranchListFilterRoute',
        '_handleBranchListOffsetRoute',
        '_handleChangeNumberLegacyRoute',
        '_handleChangeOrDiffRoute',
        '_handleDefaultRoute',
        '_handleChangeLegacyRoute',
        '_handleDiffLegacyRoute',
        '_handleLegacyLinenum',
        '_handleImproperlyEncodedPlusRoute',
        '_handlePassThroughRoute',
        '_handleProjectAccessRoute',
        '_handleProjectListFilterOffsetRoute',
        '_handleProjectListFilterRoute',
        '_handleProjectListOffsetRoute',
        '_handleProjectRoute',
        '_handleQueryLegacySuffixRoute',
        '_handleQueryRoute',
        '_handleRegisterRoute',
        '_handleTagListFilterOffsetRoute',
        '_handleTagListFilterRoute',
        '_handleTagListOffsetRoute',
      ];

      // Handler names that check authentication themselves, and thus don't need
      // it performed for them.
      const selfAuthenticatingHandlers = [
        '_handleDashboardRoute',
        '_handleCustomDashboardRoute',
        '_handleRootRoute',
      ];

      const shouldNotRequireAuth = unauthenticatedHandlers
          .concat(selfAuthenticatingHandlers);
      shouldNotRequireAuth.sort();

      assert.deepEqual(actualDoesNotRequireAuth, shouldNotRequireAuth);
    });

    test('_redirectIfNotLoggedIn while logged in', () => {
      sandbox.stub(element.$.restAPI, 'getLoggedIn')
          .returns(Promise.resolve(true));
      const data = {canonicalPath: ''};
      const redirectStub = sandbox.stub(element, '_redirectToLogin');
      return element._redirectIfNotLoggedIn(data).then(() => {
        assert.isFalse(redirectStub.called);
      });
    });

    test('_redirectIfNotLoggedIn while logged out', () => {
      sandbox.stub(element.$.restAPI, 'getLoggedIn')
          .returns(Promise.resolve(false));
      const redirectStub = sandbox.stub(element, '_redirectToLogin');
      const data = {canonicalPath: ''};
      return new Promise(resolve => {
        element._redirectIfNotLoggedIn(data)
            .then(() => {
              assert.isTrue(false, 'Should never execute');
            })
            .catch(() => {
              assert.isTrue(redirectStub.calledOnce);
              resolve();
            });
      });
    });

    suite('generateUrl', () => {
      test('search', () => {
        let params = {
          view: Gerrit.Nav.View.SEARCH,
          owner: 'a%b',
          project: 'c%d',
          branch: 'e%f',
          topic: 'g%h',
          statuses: ['op%en'],
        };
        assert.equal(element._generateUrl(params),
            '/q/owner:a%2525b+project:c%2525d+branch:e%2525f+' +
            'topic:"g%2525h"+status:op%2525en');

        params = {
          view: Gerrit.Nav.View.SEARCH,
          statuses: ['a', 'b', 'c'],
        };
        assert.equal(element._generateUrl(params),
            '/q/(status:a OR status:b OR status:c)');
      });

      test('change', () => {
        const params = {
          view: Gerrit.Nav.View.CHANGE,
          changeNum: '1234',
          project: 'test',
        };
        const paramsWithQuery = {
          view: Gerrit.Nav.View.CHANGE,
          changeNum: '1234',
          project: 'test',
          querystring: 'revert&foo=bar',
        };

        assert.equal(element._generateUrl(params), '/c/test/+/1234');
        assert.equal(element._generateUrl(paramsWithQuery),
            '/c/test/+/1234?revert&foo=bar');

        params.patchNum = 10;
        assert.equal(element._generateUrl(params), '/c/test/+/1234/10');
        paramsWithQuery.patchNum = 10;
        assert.equal(element._generateUrl(paramsWithQuery),
            '/c/test/+/1234/10?revert&foo=bar');

        params.basePatchNum = 5;
        assert.equal(element._generateUrl(params), '/c/test/+/1234/5..10');
        paramsWithQuery.basePatchNum = 5;
        assert.equal(element._generateUrl(paramsWithQuery),
            '/c/test/+/1234/5..10?revert&foo=bar');
      });

      test('diff', () => {
        const params = {
          view: Gerrit.Nav.View.DIFF,
          changeNum: '42',
          path: 'x+y/path.cpp',
          patchNum: 12,
        };
        assert.equal(element._generateUrl(params),
            '/c/42/12/x%252By/path.cpp');

        params.project = 'test';
        assert.equal(element._generateUrl(params),
            '/c/test/+/42/12/x%252By/path.cpp');

        params.basePatchNum = 6;
        assert.equal(element._generateUrl(params),
            '/c/test/+/42/6..12/x%252By/path.cpp');

        params.path = 'foo bar/my+file.txt%';
        params.patchNum = 2;
        delete params.basePatchNum;
        assert.equal(element._generateUrl(params),
            '/c/test/+/42/2/foo+bar/my%252Bfile.txt%2525');

        params.path = 'file.cpp';
        params.lineNum = 123;
        assert.equal(element._generateUrl(params),
            '/c/test/+/42/2/file.cpp#123');

        params.leftSide = true;
        assert.equal(element._generateUrl(params),
            '/c/test/+/42/2/file.cpp#b123');
      });

      test('edit', () => {
        const params = {
          view: Gerrit.Nav.View.EDIT,
          changeNum: '42',
          project: 'test',
          path: 'x+y/path.cpp',
        };
        assert.equal(element._generateUrl(params),
            '/c/test/+/42/x%252By/path.cpp,edit');
      });

      test('_getPatchRangeExpression', () => {
        const params = {};
        let actual = element._getPatchRangeExpression(params);
        assert.equal(actual, '');

        params.patchNum = 4;
        actual = element._getPatchRangeExpression(params);
        assert.equal(actual, '4');

        params.basePatchNum = 2;
        actual = element._getPatchRangeExpression(params);
        assert.equal(actual, '2..4');

        delete params.patchNum;
        actual = element._getPatchRangeExpression(params);
        assert.equal(actual, '2..');
      });

      suite('dashboard', () => {
        test('self dashboard', () => {
          const params = {
            view: Gerrit.Nav.View.DASHBOARD,
          };
          assert.equal(element._generateUrl(params), '/dashboard/self');
        });

        test('user dashboard', () => {
          const params = {
            view: Gerrit.Nav.View.DASHBOARD,
            user: 'user',
          };
          assert.equal(element._generateUrl(params), '/dashboard/user');
        });

        test('custom self dashboard, no title', () => {
          const params = {
            view: Gerrit.Nav.View.DASHBOARD,
            sections: [
              {name: 'section 1', query: 'query 1'},
              {name: 'section 2', query: 'query 2'},
            ],
          };
          assert.equal(
              element._generateUrl(params),
              '/dashboard/?section%201=query%201&section%202=query%202');
        });

        test('custom user dashboard, with title', () => {
          const params = {
            view: Gerrit.Nav.View.DASHBOARD,
            user: 'user',
            sections: [{name: 'name', query: 'query'}],
            title: 'custom dashboard',
          };
          assert.equal(
              element._generateUrl(params),
              '/dashboard/user?name=query&title=custom%20dashboard');
        });
      });

      suite('groups', () => {
        test('group info', () => {
          const params = {
            view: Gerrit.Nav.View.GROUP,
            groupId: 1234,
          };
          assert.equal(element._generateUrl(params), '/admin/groups/1234');
        });

        test('group members', () => {
          const params = {
            view: Gerrit.Nav.View.GROUP,
            groupId: 1234,
            detail: 'members',
          };
          assert.equal(element._generateUrl(params),
              '/admin/groups/1234,members');
        });

        test('group audit log', () => {
          const params = {
            view: Gerrit.Nav.View.GROUP,
            groupId: 1234,
            detail: 'log',
          };
          assert.equal(element._generateUrl(params),
              '/admin/groups/1234,audit-log');
        });
      });
    });

    suite('param normalization', () => {
      let projectLookupStub;

      setup(() => {
        projectLookupStub = sandbox
            .stub(element.$.restAPI, 'getFromProjectLookup');
        sandbox.stub(element, '_generateUrl');
      });

      suite('_normalizeLegacyRouteParams', () => {
        let rangeStub;
        let redirectStub;

        setup(() => {
          rangeStub = sandbox.stub(element, '_normalizePatchRangeParams')
              .returns(Promise.resolve());
          redirectStub = sandbox.stub(element, '_redirect');
        });

        test('w/o changeNum', () => {
          projectLookupStub.returns(Promise.resolve('foo/bar'));
          const params = {};
          return element._normalizeLegacyRouteParams(params).then(() => {
            assert.isFalse(projectLookupStub.called);
            assert.isFalse(rangeStub.called);
            assert.isNotOk(params.project);
            assert.isFalse(redirectStub.called);
          });
        });

        test('w/ changeNum', () => {
          projectLookupStub.returns(Promise.resolve('foo/bar'));
          const params = {changeNum: 1234};
          return element._normalizeLegacyRouteParams(params).then(() => {
            assert.isTrue(projectLookupStub.called);
            assert.isTrue(rangeStub.called);
            assert.equal(params.project, 'foo/bar');
            assert.isTrue(redirectStub.calledOnce);
          });
        });

        test('halts on project lookup failure', () => {
          projectLookupStub.returns(Promise.resolve(undefined));

          const params = {changeNum: 1234};
          return element._normalizeLegacyRouteParams(params).then(() => {
            assert.isTrue(projectLookupStub.called);
            assert.isFalse(rangeStub.called);
            assert.isUndefined(params.project);
            assert.isFalse(redirectStub.called);
          });
        });
      });

      suite('_normalizePatchRangeParams', () => {
        test('range n..n normalizes to n', () => {
          const params = {basePatchNum: 4, patchNum: 4};
          const needsRedirect = element._normalizePatchRangeParams(params);
          assert.isTrue(needsRedirect);
          assert.isNotOk(params.basePatchNum);
          assert.equal(params.patchNum, 4);
        });

        test('range n.. normalizes to n', () => {
          const params = {basePatchNum: 4};
          const needsRedirect = element._normalizePatchRangeParams(params);
          assert.isFalse(needsRedirect);
          assert.isNotOk(params.basePatchNum);
          assert.equal(params.patchNum, 4);
        });

        test('range 0..n normalizes to edit..n', () => {
          const params = {basePatchNum: 0, patchNum: 4};
          const needsRedirect = element._normalizePatchRangeParams(params);
          assert.isTrue(needsRedirect);
          assert.equal(params.basePatchNum, 'edit');
          assert.equal(params.patchNum, 4);
        });

        test('range n..0 normalizes to n..edit', () => {
          const params = {basePatchNum: 4, patchNum: 0};
          const needsRedirect = element._normalizePatchRangeParams(params);
          assert.isTrue(needsRedirect);
          assert.equal(params.basePatchNum, 4);
          assert.equal(params.patchNum, 'edit');
        });

        test('range 0..0 normalizes to edit', () => {
          const params = {basePatchNum: 0, patchNum: 0};
          const needsRedirect = element._normalizePatchRangeParams(params);
          assert.isTrue(needsRedirect);
          assert.isNotOk(params.basePatchNum);
          assert.equal(params.patchNum, 'edit');
        });

        // TODO(issue 4760): Remove when PG supports diffing against numbered
        // parents of a merge.
        test('range -n..m normalizes to m', () => {
          const params = {basePatchNum: -2, patchNum: 4};
          const needsRedirect = element._normalizePatchRangeParams(params);
          assert.isTrue(needsRedirect);
          assert.isNotOk(params.basePatchNum);
          assert.equal(params.patchNum, 4);
        });
      });
    });

    suite('route handlers', () => {
      let redirectStub;
      let setParamsStub;

      // Simple route handlers are direct mappings from parsed route data to a
      // new set of app.params. This test helper asserts that passing `data`
      // into `methodName` results in setting the params specified in `params`.
      function assertDataToParams(data, methodName, params) {
        element[methodName](data);
        assert.deepEqual(setParamsStub.lastCall.args[0], params);
      }

      setup(() => {
        redirectStub = sandbox.stub(element, '_redirect');
        setParamsStub = sandbox.stub(element, '_setParams');
      });

      test('_handleAgreementsRoute', () => {
        element._handleAgreementsRoute({params: {}});
        assert.isTrue(setParamsStub.calledOnce);
        assert.equal(setParamsStub.lastCall.args[0].view,
            Gerrit.Nav.View.AGREEMENTS);
      });

      test('_handleSettingsLegacyRoute', () => {
        const data = {params: {0: 'my-token'}};
        assertDataToParams(data, '_handleSettingsLegacyRoute', {
          view: Gerrit.Nav.View.SETTINGS,
          emailToken: 'my-token',
        });
      });

      test('_handleSettingsRoute', () => {
        const data = {};
        assertDataToParams(data, '_handleSettingsRoute', {
          view: Gerrit.Nav.View.SETTINGS,
        });
      });

      test('_handleDefaultRoute', () => {
        element._app = {dispatchEvent: sinon.stub()};
        element._handleDefaultRoute();
        assert.isTrue(element._app.dispatchEvent.calledOnce);
        assert.equal(
            element._app.dispatchEvent.lastCall.args[0].detail.response.status,
            404);
      });

      test('_handleImproperlyEncodedPlusRoute', () => {
        // Regression test for Issue 7100.
        element._handleImproperlyEncodedPlusRoute(
            {canonicalPath: '/c/test/%20/42', params: ['test', '42']});
        assert.isTrue(redirectStub.calledOnce);
        assert.equal(
            redirectStub.lastCall.args[0],
            '/c/test/+/42');

        sandbox.stub(element, '_getHashFromCanonicalPath').returns('foo');
        element._handleImproperlyEncodedPlusRoute(
            {canonicalPath: '/c/test/%20/42', params: ['test', '42']});
        assert.equal(
            redirectStub.lastCall.args[0],
            '/c/test/+/42#foo');
      });

      test('_handleQueryRoute', () => {
        const data = {params: ['project:foo/bar/baz']};
        assertDataToParams(data, '_handleQueryRoute', {
          view: Gerrit.Nav.View.SEARCH,
          query: 'project:foo/bar/baz',
          offset: undefined,
        });

        data.params.push(',123', '123');
        assertDataToParams(data, '_handleQueryRoute', {
          view: Gerrit.Nav.View.SEARCH,
          query: 'project:foo/bar/baz',
          offset: '123',
        });
      });

      test('_handleQueryLegacySuffixRoute', () => {
        element._handleQueryLegacySuffixRoute({path: '/q/foo+bar,n,z'});
        assert.isTrue(redirectStub.calledOnce);
        assert.equal(redirectStub.lastCall.args[0], '/q/foo+bar');
      });

      suite('_handleRegisterRoute', () => {
        test('happy path', () => {
          const ctx = {params: ['/foo/bar']};
          element._handleRegisterRoute(ctx);
          assert.isTrue(redirectStub.calledWithExactly('/foo/bar'));
          assert.isTrue(setParamsStub.calledOnce);
          assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
        });

        test('no param', () => {
          const ctx = {params: ['']};
          element._handleRegisterRoute(ctx);
          assert.isTrue(redirectStub.calledWithExactly('/'));
          assert.isTrue(setParamsStub.calledOnce);
          assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
        });

        test('prevent redirect', () => {
          const ctx = {params: ['/register']};
          element._handleRegisterRoute(ctx);
          assert.isTrue(redirectStub.calledWithExactly('/'));
          assert.isTrue(setParamsStub.calledOnce);
          assert.isTrue(setParamsStub.lastCall.args[0].justRegistered);
        });
      });

      suite('_handleRootRoute', () => {
        test('closes for closeAfterLogin', () => {
          const data = {querystring: 'closeAfterLogin', canonicalPath: ''};
          const closeStub = sandbox.stub(window, 'close');
          const result = element._handleRootRoute(data);
          assert.isNotOk(result);
          assert.isTrue(closeStub.called);
          assert.isFalse(redirectStub.called);
        });

        test('redirects to dashboard if logged in', () => {
          sandbox.stub(element.$.restAPI, 'getLoggedIn')
              .returns(Promise.resolve(true));
          const data = {
            canonicalPath: '/', path: '/', querystring: '', hash: '',
          };
          const result = element._handleRootRoute(data);
          assert.isOk(result);
          return result.then(() => {
            assert.isTrue(redirectStub.calledWithExactly('/dashboard/self'));
          });
        });

        test('redirects to open changes if not logged in', () => {
          sandbox.stub(element.$.restAPI, 'getLoggedIn')
              .returns(Promise.resolve(false));
          const data = {
            canonicalPath: '/', path: '/', querystring: '', hash: '',
          };
          const result = element._handleRootRoute(data);
          assert.isOk(result);
          return result.then(() => {
            assert.isTrue(redirectStub.calledWithExactly('/q/status:open'));
          });
        });

        suite('GWT hash-path URLs', () => {
          test('redirects hash-path URLs', () => {
            const data = {
              canonicalPath: '/#/foo/bar/baz',
              hash: '/foo/bar/baz',
              querystring: '',
            };
            const result = element._handleRootRoute(data);
            assert.isNotOk(result);
            assert.isTrue(redirectStub.called);
            assert.isTrue(redirectStub.calledWithExactly('/foo/bar/baz'));
          });

          test('redirects hash-path URLs w/o leading slash', () => {
            const data = {
              canonicalPath: '/#foo/bar/baz',
              querystring: '',
              hash: 'foo/bar/baz',
            };
            const result = element._handleRootRoute(data);
            assert.isNotOk(result);
            assert.isTrue(redirectStub.called);
            assert.isTrue(redirectStub.calledWithExactly('/foo/bar/baz'));
          });

          test('normalizes "/ /" in hash to "/+/"', () => {
            const data = {
              canonicalPath: '/#/foo/bar/+/123/4',
              querystring: '',
              hash: '/foo/bar/ /123/4',
            };
            const result = element._handleRootRoute(data);
            assert.isNotOk(result);
            assert.isTrue(redirectStub.called);
            assert.isTrue(redirectStub.calledWithExactly('/foo/bar/+/123/4'));
          });

          test('prepends baseurl to hash-path', () => {
            const data = {
              canonicalPath: '/#/foo/bar',
              querystring: '',
              hash: '/foo/bar',
            };
            sandbox.stub(element, 'getBaseUrl').returns('/baz');
            const result = element._handleRootRoute(data);
            assert.isNotOk(result);
            assert.isTrue(redirectStub.called);
            assert.isTrue(redirectStub.calledWithExactly('/baz/foo/bar'));
          });

          test('normalizes /VE/ settings hash-paths', () => {
            const data = {
              canonicalPath: '/#/VE/foo/bar',
              querystring: '',
              hash: '/VE/foo/bar',
            };
            const result = element._handleRootRoute(data);
            assert.isNotOk(result);
            assert.isTrue(redirectStub.called);
            assert.isTrue(redirectStub.calledWithExactly(
                '/settings/VE/foo/bar'));
          });

          test('does not drop "inner hashes"', () => {
            const data = {
              canonicalPath: '/#/foo/bar#baz',
              querystring: '',
              hash: '/foo/bar',
            };
            const result = element._handleRootRoute(data);
            assert.isNotOk(result);
            assert.isTrue(redirectStub.called);
            assert.isTrue(redirectStub.calledWithExactly('/foo/bar#baz'));
          });
        });
      });

      suite('_handleDashboardRoute', () => {
        let redirectToLoginStub;

        setup(() => {
          redirectToLoginStub = sandbox.stub(element, '_redirectToLogin');
        });

        test('own dashboard but signed out redirects to login', () => {
          sandbox.stub(element.$.restAPI, 'getLoggedIn')
              .returns(Promise.resolve(false));
          const data = {canonicalPath: '/dashboard/', params: {0: 'seLF'}};
          return element._handleDashboardRoute(data, '').then(() => {
            assert.isTrue(redirectToLoginStub.calledOnce);
            assert.isFalse(redirectStub.called);
            assert.isFalse(setParamsStub.called);
          });
        });

        test('non-self dashboard but signed out does not redirect', () => {
          sandbox.stub(element.$.restAPI, 'getLoggedIn')
              .returns(Promise.resolve(false));
          const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
          return element._handleDashboardRoute(data, '').then(() => {
            assert.isFalse(redirectToLoginStub.called);
            assert.isFalse(setParamsStub.called);
            assert.isTrue(redirectStub.calledOnce);
            assert.equal(redirectStub.lastCall.args[0], '/q/owner:foo');
          });
        });

        test('dashboard while signed in sets params', () => {
          sandbox.stub(element.$.restAPI, 'getLoggedIn')
              .returns(Promise.resolve(true));
          const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
          return element._handleDashboardRoute(data, '').then(() => {
            assert.isFalse(redirectToLoginStub.called);
            assert.isFalse(redirectStub.called);
            assert.isTrue(setParamsStub.calledOnce);
            assert.deepEqual(setParamsStub.lastCall.args[0], {
              view: Gerrit.Nav.View.DASHBOARD,
              user: 'foo',
            });
          });
        });
      });

      suite('_handleCustomDashboardRoute', () => {
        let redirectToLoginStub;

        setup(() => {
          redirectToLoginStub = sandbox.stub(element, '_redirectToLogin');
        });

        test('no user specified', () => {
          const data = {canonicalPath: '/dashboard/', params: {0: ''}};
          return element._handleCustomDashboardRoute(data, '').then(() => {
            assert.isFalse(setParamsStub.called);
            assert.isTrue(redirectStub.called);
            assert.equal(redirectStub.lastCall.args[0], '/dashboard/self');
          });
        });

        test('custom dashboard without title', () => {
          const data = {canonicalPath: '/dashboard/', params: {0: ''}};
          return element._handleCustomDashboardRoute(data, '?a=b&c&d=e')
              .then(() => {
                assert.isFalse(redirectStub.called);
                assert.isTrue(setParamsStub.calledOnce);
                assert.deepEqual(setParamsStub.lastCall.args[0], {
                  view: Gerrit.Nav.View.DASHBOARD,
                  user: 'self',
                  sections: [
                    {name: 'a', query: 'b'},
                    {name: 'd', query: 'e'},
                  ],
                  title: 'Custom Dashboard',
                });
              });
        });

        test('custom dashboard with title', () => {
          const data = {canonicalPath: '/dashboard/', params: {0: ''}};
          return element._handleCustomDashboardRoute(data,
              '?a=b&c&d=&=e&title=t')
              .then(() => {
                assert.isFalse(redirectToLoginStub.called);
                assert.isFalse(redirectStub.called);
                assert.isTrue(setParamsStub.calledOnce);
                assert.deepEqual(setParamsStub.lastCall.args[0], {
                  view: Gerrit.Nav.View.DASHBOARD,
                  user: 'self',
                  sections: [
                    {name: 'a', query: 'b'},
                  ],
                  title: 't',
                });
              });
        });
      });

      suite('group routes', () => {
        test('_handleGroupInfoRoute', () => {
          const data = {params: {0: 1234}};
          element._handleGroupInfoRoute(data);
          assert.isTrue(redirectStub.calledOnce);
          assert.equal(redirectStub.lastCall.args[0], '/admin/groups/1234');
        });

        test('_handleGroupAuditLogRoute', () => {
          const data = {params: {0: 1234}};
          assertDataToParams(data, '_handleGroupAuditLogRoute', {
            view: Gerrit.Nav.View.GROUP,
            detail: 'log',
            groupId: 1234,
          });
        });

        test('_handleGroupMembersRoute', () => {
          const data = {params: {0: 1234}};
          assertDataToParams(data, '_handleGroupMembersRoute', {
            view: Gerrit.Nav.View.GROUP,
            detail: 'members',
            groupId: 1234,
          });
        });

        test('_handleGroupListOffsetRoute', () => {
          const data = {params: {}};
          assertDataToParams(data, '_handleGroupListOffsetRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-admin-group-list',
            offset: 0,
            filter: null,
            openCreateModal: false,
          });

          data.params[1] = 42;
          assertDataToParams(data, '_handleGroupListOffsetRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-admin-group-list',
            offset: 42,
            filter: null,
            openCreateModal: false,
          });

          data.hash = 'create';
          assertDataToParams(data, '_handleGroupListOffsetRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-admin-group-list',
            offset: 42,
            filter: null,
            openCreateModal: true,
          });
        });

        test('_handleGroupListFilterOffsetRoute', () => {
          const data = {params: {filter: 'foo', offset: 42}};
          assertDataToParams(data, '_handleGroupListFilterOffsetRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-admin-group-list',
            offset: 42,
            filter: 'foo',
          });
        });

        test('_handleGroupListFilterRoute', () => {
          const data = {params: {filter: 'foo'}};
          assertDataToParams(data, '_handleGroupListFilterRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-admin-group-list',
            filter: 'foo',
          });
        });

        test('_handleGroupRoute', () => {
          const data = {params: {0: 4321}};
          assertDataToParams(data, '_handleGroupRoute', {
            view: Gerrit.Nav.View.GROUP,
            groupId: 4321,
          });
        });
      });

      suite('project routes', () => {
        test('_handleProjectRoute', () => {
          const data = {params: {0: 4321}};
          assertDataToParams(data, '_handleProjectRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-project',
            project: 4321,
          });
        });

        test('_handleProjectCommandsRoute', () => {
          const data = {params: {0: 4321}};
          assertDataToParams(data, '_handleProjectCommandsRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-project-commands',
            detailType: 'commands',
            project: 4321,
          });
        });

        test('_handleProjectAccessRoute', () => {
          const data = {params: {0: 4321}};
          assertDataToParams(data, '_handleProjectAccessRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-project-access',
            detailType: 'access',
            project: 4321,
          });
        });

        suite('branch list routes', () => {
          test('_handleBranchListOffsetRoute', () => {
            const data = {params: {0: 4321}};
            assertDataToParams(data, '_handleBranchListOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-detail-list',
              detailType: 'branches',
              project: 4321,
              offset: 0,
              filter: null,
            });

            data.params[2] = 42;
            assertDataToParams(data, '_handleBranchListOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-detail-list',
              detailType: 'branches',
              project: 4321,
              offset: 42,
              filter: null,
            });
          });

          test('_handleBranchListFilterOffsetRoute', () => {
            const data = {params: {project: 4321, filter: 'foo', offset: 42}};
            assertDataToParams(data, '_handleBranchListFilterOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-detail-list',
              detailType: 'branches',
              project: 4321,
              offset: 42,
              filter: 'foo',
            });
          });

          test('_handleBranchListFilterRoute', () => {
            const data = {params: {project: 4321, filter: 'foo'}};
            assertDataToParams(data, '_handleBranchListFilterRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-detail-list',
              detailType: 'branches',
              project: 4321,
              filter: 'foo',
            });
          });
        });

        suite('tag list routes', () => {
          test('_handleTagListOffsetRoute', () => {
            const data = {params: {0: 4321}};
            assertDataToParams(data, '_handleTagListOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-detail-list',
              detailType: 'tags',
              project: 4321,
              offset: 0,
              filter: null,
            });
          });

          test('_handleTagListFilterOffsetRoute', () => {
            const data = {params: {project: 4321, filter: 'foo', offset: 42}};
            assertDataToParams(data, '_handleTagListFilterOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-detail-list',
              detailType: 'tags',
              project: 4321,
              offset: 42,
              filter: 'foo',
            });
          });

          test('_handleTagListFilterRoute', () => {
            const data = {params: {project: 4321}};
            assertDataToParams(data, '_handleTagListFilterRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-detail-list',
              detailType: 'tags',
              project: 4321,
              filter: null,
            });

            data.params.filter = 'foo';
            assertDataToParams(data, '_handleTagListFilterRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-detail-list',
              detailType: 'tags',
              project: 4321,
              filter: 'foo',
            });
          });
        });

        suite('project list routes', () => {
          test('_handleProjectListOffsetRoute', () => {
            const data = {params: {}};
            assertDataToParams(data, '_handleProjectListOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-list',
              offset: 0,
              filter: null,
              openCreateModal: false,
            });

            data.params[1] = 42;
            assertDataToParams(data, '_handleProjectListOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-list',
              offset: 42,
              filter: null,
              openCreateModal: false,
            });

            data.hash = 'create';
            assertDataToParams(data, '_handleProjectListOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-list',
              offset: 42,
              filter: null,
              openCreateModal: true,
            });
          });

          test('_handleProjectListFilterOffsetRoute', () => {
            const data = {params: {filter: 'foo', offset: 42}};
            assertDataToParams(data, '_handleProjectListFilterOffsetRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-list',
              offset: 42,
              filter: 'foo',
            });
          });

          test('_handleProjectListFilterRoute', () => {
            const data = {params: {}};
            assertDataToParams(data, '_handleProjectListFilterRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-list',
              filter: null,
            });

            data.params.filter = 'foo';
            assertDataToParams(data, '_handleProjectListFilterRoute', {
              view: Gerrit.Nav.View.ADMIN,
              adminView: 'gr-project-list',
              filter: 'foo',
            });
          });
        });
      });

      suite('plugin routes', () => {
        test('_handlePluginListOffsetRoute', () => {
          const data = {params: {}};
          assertDataToParams(data, '_handlePluginListOffsetRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-plugin-list',
            offset: 0,
            filter: null,
          });

          data.params[1] = 42;
          assertDataToParams(data, '_handlePluginListOffsetRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-plugin-list',
            offset: 42,
            filter: null,
          });
        });

        test('_handlePluginListFilterOffsetRoute', () => {
          const data = {params: {filter: 'foo', offset: 42}};
          assertDataToParams(data, '_handlePluginListFilterOffsetRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-plugin-list',
            offset: 42,
            filter: 'foo',
          });
        });

        test('_handlePluginListFilterRoute', () => {
          const data = {params: {}};
          assertDataToParams(data, '_handlePluginListFilterRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-plugin-list',
            filter: null,
          });

          data.params.filter = 'foo';
          assertDataToParams(data, '_handlePluginListFilterRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-plugin-list',
            filter: 'foo',
          });
        });

        test('_handlePluginListRoute', () => {
          const data = {params: {}};
          assertDataToParams(data, '_handlePluginListRoute', {
            view: Gerrit.Nav.View.ADMIN,
            adminView: 'gr-plugin-list',
          });
        });
      });

      suite('change/diff routes', () => {
        test('_handleChangeNumberLegacyRoute', () => {
          const data = {params: {0: 12345}};
          element._handleChangeNumberLegacyRoute(data);
          assert.isTrue(redirectStub.calledOnce);
          assert.isTrue(redirectStub.calledWithExactly('/c/12345'));
        });

        test('_handleChangeLegacyRoute', () => {
          const normalizeRouteStub = sandbox.stub(element,
              '_normalizeLegacyRouteParams');
          const ctx = {
            params: [
              1234, // 0 Change number
              null, // 1 Unused
              null, // 2 Unused
              6, // 3 Base patch number
              null, // 4 Unused
              9, // 5 Patch number
            ],
            querystring: '',
          };
          element._handleChangeLegacyRoute(ctx);
          assert.isTrue(normalizeRouteStub.calledOnce);
          assert.deepEqual(normalizeRouteStub.lastCall.args[0], {
            changeNum: 1234,
            basePatchNum: 6,
            patchNum: 9,
            view: Gerrit.Nav.View.CHANGE,
            querystring: '',
          });
        });

        test('_handleDiffLegacyRoute', () => {
          const normalizeRouteStub = sandbox.stub(element,
              '_normalizeLegacyRouteParams');
          const ctx = {
            params: [
              1234, // 0 Change number
              null, // 1 Unused
              3, // 2 Base patch number
              null, // 3 Unused
              8, // 4 Patch number
              'foo/bar', // 5 Diff path
            ],
            path: '/c/1234/3..8/foo/bar',
            hash: 'b123',
          };
          element._handleDiffLegacyRoute(ctx);
          assert.isFalse(redirectStub.called);
          assert.isTrue(normalizeRouteStub.calledOnce);
          assert.deepEqual(normalizeRouteStub.lastCall.args[0], {
            changeNum: 1234,
            basePatchNum: 3,
            patchNum: 8,
            view: Gerrit.Nav.View.DIFF,
            path: 'foo/bar',
            lineNum: 123,
            leftSide: true,
          });
        });

        test('_handleLegacyLinenum w/ @321', () => {
          const ctx = {path: '/c/1234/3..8/foo/bar@321'};
          element._handleLegacyLinenum(ctx);
          assert.isTrue(redirectStub.calledOnce);
          assert.isTrue(redirectStub.calledWithExactly(
              '/c/1234/3..8/foo/bar#321'));
        });

        test('_handleLegacyLinenum w/ @b123', () => {
          const ctx = {path: '/c/1234/3..8/foo/bar@b123'};
          element._handleLegacyLinenum(ctx);
          assert.isTrue(redirectStub.calledOnce);
          assert.isTrue(redirectStub.calledWithExactly(
              '/c/1234/3..8/foo/bar#b123'));
        });

        suite('_handleChangeOrDiffRoute', () => {
          let normalizeRangeStub;

          function makeParams(path, hash) {
            return {
              params: [
                'foo/bar', // 0 Project
                1234, // 1 Change number
                null, // 2 Unused
                null, // 3 Unused
                4, // 4 Base patch number
                null, // 5 Unused
                7, // 6 Patch number
                null, // 7 Unused,
                path, // 8 Diff path
              ],
              hash,
            };
          }

          setup(() => {
            normalizeRangeStub = sandbox.stub(element,
                '_normalizePatchRangeParams');
            sandbox.stub(element.$.restAPI, 'setInProjectLookup');
          });

          test('needs redirect', () => {
            normalizeRangeStub.returns(true);
            sandbox.stub(element, '_generateUrl').returns('foo');
            const ctx = makeParams(null, '');
            element._handleChangeOrDiffRoute(ctx);
            assert.isTrue(normalizeRangeStub.called);
            assert.isFalse(setParamsStub.called);
            assert.isTrue(redirectStub.calledOnce);
            assert.isTrue(redirectStub.calledWithExactly('foo'));
          });

          test('change view', () => {
            normalizeRangeStub.returns(false);
            sandbox.stub(element, '_generateUrl').returns('foo');
            const ctx = makeParams(null, '');
            assertDataToParams(ctx, '_handleChangeOrDiffRoute', {
              view: Gerrit.Nav.View.CHANGE,
              project: 'foo/bar',
              changeNum: 1234,
              basePatchNum: 4,
              patchNum: 7,
              path: null,
            });
            assert.isFalse(redirectStub.called);
            assert.isTrue(normalizeRangeStub.called);
          });

          test('diff view', () => {
            normalizeRangeStub.returns(false);
            sandbox.stub(element, '_generateUrl').returns('foo');
            const ctx = makeParams('foo/bar/baz', 'b44');
            assertDataToParams(ctx, '_handleChangeOrDiffRoute', {
              view: Gerrit.Nav.View.DIFF,
              project: 'foo/bar',
              changeNum: 1234,
              basePatchNum: 4,
              patchNum: 7,
              path: 'foo/bar/baz',
              leftSide: true,
              lineNum: 44,
            });
            assert.isFalse(redirectStub.called);
            assert.isTrue(normalizeRangeStub.called);
          });
        });

        test('_handleDiffEditRoute', () => {
          const normalizeRangeSpy =
              sandbox.spy(element, '_normalizePatchRangeParams');
          sandbox.stub(element.$.restAPI, 'setInProjectLookup');
          const ctx = {
            params: [
              'foo/bar', // 0 Project
              1234, // 1 Change number
              'foo/bar/baz', // 2 File path
            ],
          };
          const appParams = {
            project: 'foo/bar',
            changeNum: 1234,
            view: Gerrit.Nav.View.EDIT,
            path: 'foo/bar/baz',
          };

          element._handleDiffEditRoute(ctx);
          assert.isFalse(redirectStub.called);
          assert.isTrue(normalizeRangeSpy.calledOnce);
          assert.deepEqual(normalizeRangeSpy.lastCall.args[0], appParams);
          assert.isFalse(normalizeRangeSpy.lastCall.returnValue);
          assert.deepEqual(setParamsStub.lastCall.args[0], appParams);
        });
      });
    });

    suite('_parseQueryString', () => {
      test('empty queries', () => {
        assert.deepEqual(element._parseQueryString(''), []);
        assert.deepEqual(element._parseQueryString('?'), []);
        assert.deepEqual(element._parseQueryString('??'), []);
        assert.deepEqual(element._parseQueryString('&&&'), []);
      });

      test('url decoding', () => {
        assert.deepEqual(element._parseQueryString('+'), [[' ', '']]);
        assert.deepEqual(element._parseQueryString('???+%3d+'), [[' = ', '']]);
        assert.deepEqual(
            element._parseQueryString('%6e%61%6d%65=%76%61%6c%75%65'),
            [['name', 'value']]);
      });

      test('multiple parameters', () => {
        assert.deepEqual(
            element._parseQueryString('a=b&c=d&e=f'),
            [['a', 'b'], ['c', 'd'], ['e', 'f']]);
        assert.deepEqual(
            element._parseQueryString('&a=b&&&e=f&'),
            [['a', 'b'], ['e', 'f']]);
      });
    });
  });
</script>
